﻿using Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using S7.Net;
using SqlSugar.IOC;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using ZR.Model.Models.ProductionControl;
using CKS.PLC.Core.Event;
using CKS.Core.Framework;
using ZR.Infrastructure;
using System.Drawing;

namespace CKS.PLC.Core.Siemens
{
    public abstract class SiemensClientBase<T>: ISiemensClient
    {
        public PlcConfig PlcInfo { get; set; }
       public IServiceProvider serviceProvider;
       public ILogger<T> logger;
      
       public IFreeSql<MesDB> fsql = null;
        public SiemensClientBase(PlcConfig _config, IServiceProvider _serviceProvider)
        {
            this.PlcInfo = _config;
            serviceProvider = _serviceProvider;
            logger = serviceProvider.GetService<ILogger<T>>();
            fsql = serviceProvider.GetService<IFreeSql<MesDB>>();
            try
            {
               // db = DbTransient.Sugar;
                //logger.LogInformation($"{_config.WorkstationCode} MySql: {db.ContextID}");
            }
            catch (Exception)
            {

            }

        }

        public T plcSession;

        private List<CksPlcDataPosition> AllDataPosition = null;       //所有点位
        private List<CksPlcDataPosition> SubscriptionPosition = null;  //订阅点位
        private DateTime LastHeartTime = DateTime.Now;


        public class DataBlock
        {
            public DataBlock(int blockDb, int blockLenth, Task<byte[]> readTask, SieminDecoder decoder)
            {
                BlockDb = blockDb;
                BlockLength = blockLenth;
                ReadTask = readTask;
                Decoder = decoder;
            }

            public int BlockDb { get; set; }
            public int BlockLength { get; set; }
            public Task<byte[]> ReadTask { get; set; }
            public SieminDecoder Decoder { get; set; }
        }


        /// <summary>
        /// BlockDb 数据块 
        /// BlockLenth 大小
        /// Task<byte[]> 异步读取的结果
        /// </summary>
        private List<DataBlock> DataBlocks = new List<DataBlock>();   // 需要读取的数据块


        public abstract T GetSdkInstance();
        /// <summary>
        /// 启动客户端
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public async Task StartAsync(CancellationToken cancellation)
        {
            await Task.Yield();
            GetSdkInstance();

            BindCacheData();

            _ = Execute(cancellation);
            await Task.CompletedTask;
        }

        private void BindCacheData()
        {
            //获取需要读取的数据块
            AllDataPosition = fsql.Select<CksPlcDataPosition>().Where(a => a.PlcId == PlcInfo.Id && a.Module == 1).ToList();
            SubscriptionPosition = AllDataPosition.FindAll(a => a.IsListen || a.EffectType == 3);
            //提取订阅点的数据块批量读取
            DataBlocks.Clear();
            foreach (var item in SubscriptionPosition)
            {
                var findex = DataBlocks.FindIndex(a => a.BlockDb == item.BlockDb);
                if (findex < 0)
                {
                    //该DB块内的所有数据
                    var points = AllDataPosition.FindAll(a => a.BlockDb == item.BlockDb);
                    DataBlocks.Add(new(item.BlockDb, item.BlockLength, null, new SieminDecoder(points)));
                }
            }
        }

        public abstract Task ConnectPlc(CancellationToken cancellation);

      

        /// <summary>
        /// 每x毫秒读取一次
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task Execute(CancellationToken cancellation)
        {

            try
            {
                while (!cancellation.IsCancellationRequested)
                {
                    int STEP = 1;
                    try
                    {
                        if (CheckUpdateCache() > 0)
                        {
                            await Task.Delay(1000);
                            continue;
                        }
                        STEP = 2;
                        await ConnectPlc(cancellation);

                        STEP = 3;
                        await DoHeart();
                        STEP = 4;
                        await ExecuteMain();
                    }
                    catch (Exception ex)
                    {
                        EventBus.AddLog($"{PlcInfo.WorkstationCode} STEP:{STEP} 异常{ex.GetType()}: {ex.Message}");
                        
                    }


                    await Task.Delay(30, cancellation);
                }
                await Task.CompletedTask;
            }
            catch (Exception ex)
            {
                logger.LogWarning($"异常 {ex.Message}");
            }


        }
        /// <summary>
        /// 检查并更新缓存 返回0 OK 其他值代表没有数据
        /// </summary>
        private int CheckUpdateCache()
        {
            int result = 0;
            //检查当前PLC状态是否禁用 
            //检查当前PLC的通信点位是否有变化，有变化需要重新加载
            var model = fsql.Select<PlcConfig>().Where(a => a.Id == PlcInfo.Id).First();
            if (model == null || model.IsEnabled == false)
            {
                result = 1;
            }
            PlcInfo.PlcAddress = model.PlcAddress;
            bool pointIsChange = false;
            var alls = fsql.Select<CksPlcDataPosition>().Where(a => a.PlcId == PlcInfo.Id && a.Module == 1).ToList();
            if (alls.Count != AllDataPosition.Count)
                pointIsChange = true;
            else if (alls.Count > 0)
            {
                for (int i = 0; i < alls.Count; i++)
                {
                    var id = alls[i].Id;
                    var a = alls[i];
                    var b = AllDataPosition.Find(x => x.Id == id);
                    if (b == null)
                        pointIsChange = true;
                    else if (b.BytePosition != a.BytePosition)
                        pointIsChange = true;
                    else if (b.BlockDb != a.BlockDb)
                        pointIsChange = true;
                    else if (!b.ParseType.Equals(a.ParseType))
                        pointIsChange = true;
                }
            }
            else
            {//没有点位数据
                result = 1;
            }
            if (pointIsChange)
            {
                BindCacheData();
            }
            return result;
        }
        public int HeartValue { get; set; } = 0;
        int HeartSeed = 1;
        int PlcLastHeart = -1;
        
        public bool IsConnected { get; set; } = false;
        bool LastConnected = false;
        public async Task DoHeart()
        {

            try
            {
                var ts = DateTime.Now - LastPlcHeart;
                if (ts.TotalSeconds < 5 && IsConnected)
                {
                    IsOnline = true;
                    App.AddOnlinePlcs(PlcInfo.Id);
                }
                else
                {
                    IsOnline = false;
                    App.RemoveOnlinePlcs(PlcInfo.Id);
                }


                ts = DateTime.Now - LastHeartTime;
                if (ts.TotalMilliseconds > 500)
                {
                    HeartSeed++;
                    if (HeartSeed > 1000)
                        HeartSeed = 1;
                    Int16 valueToWrite = (short)(HeartSeed % 2);
                    //查询心跳点位
                    var points = AllDataPosition.FindAll(a => a.EffectType == 2);
                    if (IsConnected)
                    {
                        foreach (var point in points)
                        {
                            SetValue(point, valueToWrite.ToString());
                            // WriteInt16(point.BlockDb, (int)point.BytePosition, valueToWrite);
                        }
                        LastHeartTime = DateTime.Now;
                        //读取PLC心跳值
                        var plcheart = AllDataPosition.Find(a => a.EffectType == 3);
                        if (plcheart != null)
                        {
                            switch (plcheart.ParseType.ToLower())
                            {
                                case "bool":
                                    int bitindex = (int)(plcheart.BytePosition * 10) % 10;
                                    var boolByte =await ReadBytesAsync(plcheart.BlockDb, (int)plcheart.BytePosition, 1);
                                    HeartValue = DataConversion.GetBit(boolByte[0], bitindex) ? 1 : 0;
                                    break;
                                default:
                                    HeartValue = ReadInt16(plcheart.BlockDb, (int)plcheart.BytePosition);
                                    break;
                            }
                           
                        }
                    }

                    if (PlcLastHeart != HeartValue || IsConnected != LastConnected)
                    {
                        //logger.Information($"变更心跳值{plcHeartValue}");
                        // EventBus.PlcHeart(this, plcHeartValue);
                        LastPlcHeart = DateTime.Now;
                        if (IsConnected == false)
                            HeartValue = 0;
                        EventBus.PlcHeart(this, HeartValue);
                    }
                    LastConnected = IsConnected;
                }
            }
            catch (Exception ex)
            {
                logger.LogWarning($"写入MES心跳异常 {ex.Message}");
            }
        }


        public abstract void WriteInt16(int db, int startByteAdr, Int16 value);
        public abstract short ReadInt16(int db, int startByteAdr);

        /// <summary>
        /// 执行订阅方法
        /// </summary>
        public void ExecuteSubscriptionAction(DecodeValue value)
        {
            var ActionBaseType = PlcActionDLLManage.GetActionBaseType();
            // 获取方法
            MethodInfo method = ActionBaseType.GetMethod("GetInstance");

            var instance = method.Invoke(null, [value]) as BasePlcAction;
            instance.InitConfig(serviceProvider);

            try
            {
                instance.Execute();
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                instance.Close();
            }


        }


        /// <summary>
        /// 读取订阅点位
        /// </summary>
        /// <returns></returns>
        public async Task ExecuteMain()
        {
            try
            {
                if (!IsConnected)
                { return; }
                if (DataBlocks.Count == 0)
                { return; }



                //获取需要读取的数据块
                // siemenPlc.ReadBytesAsync(DataType.DataBlock, item.Key, 0, item.Value);
                List<Task<byte[]>> tasks = new List<Task<byte[]>>();

                for (int i = 0; i < DataBlocks.Count; i++)
                {
                    var block = DataBlocks[i];

                    var task = ReadBytesAsync( block.BlockDb, 0, block.BlockLength);
                    block.ReadTask = task;
                    tasks.Add(task);
                }
                await Task.WhenAll(tasks);
                //
                foreach (var block in DataBlocks)
                {
                    var blockBytes = block.ReadTask.Result; //异步任务结果
                                                            //解析所有点位数据
                    var DecodeValues = block.Decoder.Decode(blockBytes);
                    SetCacheValues(block.BlockDb, DecodeValues);
                    var subscriptionCodes = DecodeValues.FindAll(a => a.Position.IsListen);
                    foreach (var val in subscriptionCodes)
                    {
                        val.AllValues = DecodeValues;
                        if (val.IsChange || val.Position.EffectType == 4)
                        {

                            val.SiemensClient = this;
                            val.AllPositions = AllDataPosition;

                            val.LinkPositions = AllDataPosition.FindAll(a => a.LinkActionId == val.Position.ActionId);
                            //  ProccessPointValue(val);
                            ExecuteSubscriptionAction(val);
                            val.OldValue = val.Value; //处理完成后将值赋给旧值变量

                        }
                    }

                }
            }
            catch (Exception ex)
            {
                logger.LogWarning($"操作plc异常 {ex.Message}{ex.StackTrace}");
            }

        }

       public Dictionary<int,List<DecodeValue>> CacheValues = new Dictionary<int,List<DecodeValue>>();

        private readonly object _lockCache = new object(); 
        public void SetCacheValues(int db, List<DecodeValue> values)
        {
            lock (_lockCache)
            {
                CacheValues[db] = values;
            }
        }

        public List<DecodeValue> GetCacheValues(int db)
        {
            lock (_lockCache)
            {
                try
                {
                    if (!IsConnected)
                        return null;
                    if (!CacheValues.ContainsKey(db))
                        return null;
                    return CacheValues[db];
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }



        public abstract Task<byte[]> ReadBytesAsync(int db, int startByteAdr, int count);

        public virtual void SetValue(CksPlcDataPosition s7Point, string configValue)
        {
            throw new Exception("实例类暂未实现");
        }

        public virtual object GetValue(CksPlcDataPosition s7Point)
        {
            throw new Exception("实例类暂未实现");
        }

        private DateTime LastPlcHeart = App.MinTime;
        /// <summary>
        /// 当前PLC是否在线
        /// </summary>
        public bool IsOnline { get; set; }
      
    
    }
}
